package com.ynet.middleground.user.model;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dozer.Mapper;
import org.redisson.api.RKeys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynet.cache.redis.IfpRedisson;
import com.ynet.core.common.BaseReqObjPager;
import com.ynet.core.common.PagerInfo;
import com.ynet.core.exception.BusinessException;
import com.ynet.middleground.user.bean.AddChannelResourceReq;
import com.ynet.middleground.user.bean.QueryChannelResourceReq;
import com.ynet.middleground.user.dao.ChannelResourcesMapper;
import com.ynet.middleground.user.dao.TableResourcesMapper;
import com.ynet.middleground.user.dto.ChannelResourcesDTO;
import com.ynet.middleground.user.dto.TableResourceDto;
import com.ynet.middleground.user.entity.ChannelResources;
import com.ynet.middleground.user.entity.TableResources;
import com.ynet.middleground.user.utils.CommUtils;

/**
 * @author liulx
 * @description 跨渠道资源配置
 * @date 2020-07-07 14:23
 */
@Component
public class ChannelResourceModel extends ServiceImpl<ChannelResourcesMapper, ChannelResources> {

    @Autowired
    private Mapper mapper;

    @Autowired
    private TableResourcesMapper tableResourcesMapper;

    @Autowired
    private ChannelConfigModel channelModel;

    @Autowired
    private IfpRedisson redisson;

    /**
     * 新增或修改跨渠道资源配置，支持批量
     * 
     * @param req 新增跨渠道资源配置请求参数
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveOrUpdate(AddChannelResourceReq req) {
        String desc = req.getDescription();
        String sourceChannelCode = req.getSourceChannelCode();
        String targetChannelCode = req.getTargetChannelCode();
        if (sourceChannelCode.equals(targetChannelCode)) {
            throw new BusinessException("原渠道与目标渠道不能相同", "ECUC0400");
        }
        List<String> tableCodeList = req.getTableCodeList();
        if (tableCodeList == null || tableCodeList.isEmpty()) {
            throw new BusinessException("跨渠道资源列表不能为空", "ECUC0001");
        }
        // 判断是否为修改操作
        if (req.getUpdateFlag() != null && req.getUpdateFlag()) {
            // 查询原资源配置，并批量删除
            delete(req.getSourceChannelCode(), req.getTargetChannelCode());
        }
        ArrayList<ChannelResources> list = new ArrayList<>();
        tableCodeList.forEach(resCode -> {
        	LambdaQueryWrapper<ChannelResources> queryWrapper = new LambdaQueryWrapper<ChannelResources>();
            queryWrapper.eq(ChannelResources::getIsDeleted, "0");
            queryWrapper.eq(ChannelResources::getTargetChannelCode, targetChannelCode);
            queryWrapper.eq(ChannelResources::getSourceChannelCode, sourceChannelCode);
            queryWrapper.eq(ChannelResources::getTableCode, resCode);
            if(this.count(queryWrapper)==0) {
            ChannelResources channelRes = new ChannelResources();
            channelRes.setSourceChannelCode(sourceChannelCode);
            channelRes.setTargetChannelCode(targetChannelCode);
            channelRes.setTableCode(resCode);
            channelRes.setDescription(desc);
            channelRes.setIsDeleted(false);
            channelRes.setGmtCreate(LocalDateTime.now());
            channelRes.setCreateBy(req.getOperationUserId());
            channelRes.setGmtModified(LocalDateTime.now());
            channelRes.setModifiedBy(req.getOperationUserId());
            list.add(channelRes);
            }
        });
        this.saveBatch(list);
    }

    /**
     * 根据原渠道号和模板渠道号查询跨渠道资源信息
     * 
     * @param sourceChannelCode 原渠道号
     * @param targetChannelCode 目标渠道号
     * @return 跨渠道资源配置传输对象
     */
    public ChannelResourcesDTO getDetail(String sourceChannelCode, String targetChannelCode) {
        LambdaQueryWrapper<ChannelResources> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChannelResources::getIsDeleted, "0");
        queryWrapper.eq(ChannelResources::getSourceChannelCode, sourceChannelCode);
        queryWrapper.eq(ChannelResources::getTargetChannelCode, targetChannelCode);
        List<ChannelResources> resourcesList = this.list(queryWrapper);

        ChannelResourcesDTO dto = new ChannelResourcesDTO();
        dto.setSourceChannel(channelModel.getDetailByCode(sourceChannelCode));
        dto.setTargetChannel(channelModel.getDetailByCode(targetChannelCode));
        if (resourcesList == null || resourcesList.isEmpty()) {
            return dto;
        }
        dto.setDescription(resourcesList.get(0).getDescription());
        List<TableResourceDto> resList = new ArrayList<>();
        resourcesList.forEach(res -> {
            LambdaQueryWrapper<TableResources> queryTableWrapper = new LambdaQueryWrapper<>();
            queryTableWrapper.eq(TableResources::getStatus, "0");
            queryTableWrapper.eq(TableResources::getTableCode, res.getTableCode());
            TableResources tableResource = tableResourcesMapper.selectOne(queryTableWrapper);
            TableResourceDto resourceDto = mapper.map(tableResource, TableResourceDto.class);
            resList.add(resourceDto);
        });
        dto.setTableCodeList(resList);
        return dto;
    }

    /**
     * 根据原渠道号和模板渠道号查询跨渠道资源信息
     * 
     * @param req 查询跨渠道资源配置详情请求参数
     * @return 跨渠道资源配置传输对象
     */
    public ChannelResourcesDTO getDetail(QueryChannelResourceReq req) {
        String sourceChannelCode = req.getSourceChannelCode();
        String targetChannelCode = req.getTargetChannelCode();
        return getDetail(sourceChannelCode, targetChannelCode);
    }

    /**
     * 根据原渠道号和模板渠道号删除两者之间跨渠道所有资源信息
     * 
     * @param req 查询跨渠道资源配置详情请求参数
     */
    public void delete(QueryChannelResourceReq req) {
        delete(req.getSourceChannelCode(), req.getTargetChannelCode());
    }

    /**
     * 根据原渠道号和模板渠道号删除两者之间跨渠道所有资源信息
     * 
     * @param sourceChannelCode 原渠道号
     * @param targetChannelCode 目标渠道号
     */
    public void delete(String sourceChannelCode, String targetChannelCode) {
        // 查询原资源配置，并批量删除
        LambdaQueryWrapper<ChannelResources> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ChannelResources::getIsDeleted, "0");
        queryWrapper.eq(ChannelResources::getTargetChannelCode, targetChannelCode);
        queryWrapper.eq(ChannelResources::getSourceChannelCode, sourceChannelCode);
        this.remove(queryWrapper);
    }

    /**
     * 分页查询跨渠道信息
     * 
     * @param req 基础参数分页
     * @return 跨渠道资源配置列表
     */
    public List<ChannelResourcesDTO> listPage(BaseReqObjPager req) {
        PagerInfo pagerInfo = req.getPagerInfo();
        QueryWrapper<ChannelResources> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("DISTINCT source_channel_code, target_channel_code");
        queryWrapper.eq("is_deleted", "0");
        Page<ChannelResources> page = new Page<>(pagerInfo.getPageNum(), pagerInfo.getPageSize());
        IPage<ChannelResources> pageResult = this.page(page, queryWrapper);
        List<ChannelResources> records = pageResult.getRecords();
        if (records == null || records.isEmpty()) {
            return new ArrayList<>(0);
        }
        List<ChannelResourcesDTO> resList = new ArrayList<>();
        records.forEach(o -> {
            ChannelResourcesDTO dto = getDetail(o.getSourceChannelCode(), o.getTargetChannelCode());
            resList.add(dto);
        });
        pagerInfo.setTotal(pageResult.getTotal());
        return resList;
    }

    /**
     * 同步跨渠道资源信息到 Redis
     */
    public void doSyncChannelResourceToRedis() {
        // 查询所有跨渠道信息
        LambdaQueryWrapper<ChannelResources> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(ChannelResources::getTableCode, ChannelResources::getSourceChannelCode,
            ChannelResources::getTargetChannelCode);
        queryWrapper.eq(ChannelResources::getIsDeleted, "0");
        queryWrapper.orderByAsc(ChannelResources::getTableCode);
        List<ChannelResources> list = this.list(queryWrapper);
        // 跨渠道资源要存于 redis 中的临时数据，key 为： 固定前缀+资源表名，value 为：<原渠道号，跨渠道号列表>
        Map<String, Map<String, List<String>>> redisMap = new HashMap<>(16);
        // 采用增强型 forEach 效率较高
        for (ChannelResources res : list) {
            /*// 生成资源的 key 值，格式为 ifp:channel:资源表名
            String resourceKey = CommUtils.getRedisKey(res.getTableCode());
            // 先从临时数据 Map 中获取有没有该资源
            Map<String, List<String>> redisValueMap = redisMap.get(resourceKey);
            if (redisValueMap == null) {
                redisValueMap = new HashMap<>(16);
            }
            // 取出该资源该渠道所要跨越的渠道列表
            List<String> targetChannelList = redisValueMap.get(res.getSourceChannelCode());
            if (targetChannelList == null) {
                targetChannelList = new ArrayList<>();
            }*/
            // 生成资源的 key 值，格式为 ifp:channel:资源表名
            String resourceKey = CommUtils.getRedisKey(res.getTableCode());
            Map<String, List<String>> redisValueMap = redisMap.get(resourceKey);
            if (redisValueMap == null) {
                redisValueMap = new HashMap<>(16);
            }

            List<String> targetChannelList = redisValueMap.get(res.getSourceChannelCode());
            if(targetChannelList == null) {
                targetChannelList = new ArrayList<>();
            }
            targetChannelList.add(res.getTargetChannelCode());

            // 重新原渠道要跨越的渠道列表存入
            redisValueMap.put(res.getSourceChannelCode(), targetChannelList);
            // 重新将跨渠道信息存入临时 Map 中
            redisMap.put(resourceKey, redisValueMap);
        }
        // 获取所有跨渠道信息key，并从 redis 中删除
        RKeys keys = redisson.getKeys();
        Iterable<String> keysByPattern = keys.getKeysByPattern("ifp:channel:*");
        keysByPattern.forEach(key -> redisson.getBucket(key).delete());
        // 将临时数据存入 redis，采用 entrSet 循环效率比较高
        for (Map.Entry<String, Map<String, List<String>>> entry : redisMap.entrySet()) {
            String key = entry.getKey();
            Map<String, List<String>> value = entry.getValue();
            redisson.set(key, value);
        }
    }

}
